package by.sjc.giz.service.impl;

import by.sjc.giz.dao.converters.OrderConverter;
import by.sjc.giz.dao.entity.OrderEntity;
import by.sjc.giz.dao.repositiory.OrderDao;
import by.sjc.giz.dao.repositiory.PublisherDao;
import by.sjc.giz.model.Account;
import by.sjc.giz.model.Cart;
import by.sjc.giz.model.Order;
import by.sjc.giz.model.OrderStatus;
import by.sjc.giz.model.Subject;
import by.sjc.giz.model.SubjectType;
import by.sjc.giz.model.BookInCart;
import by.sjc.giz.model.OrderRecord;
import by.sjc.giz.model.payment.Payment;
import by.sjc.giz.model.shop.BookInShop;
import by.sjc.giz.model.shop.BookInShopListWrapper;
import by.sjc.giz.service.BookService;
import by.sjc.giz.service.OrderService;
import by.sjc.giz.service.ShopService;
import by.sjc.giz.service.orders.OrderProcessorFactory;
import by.sjc.giz.service.orders.RecipientOrderProcessor;
import by.sjc.giz.service.orders.SenderOrderProcessor;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * Created by irina on 31.07.14.
 */
@Service
public class OrderServiceImpl implements OrderService {

    public static final Logger logger = Logger.getLogger(OrderService.class);

    @Autowired
    private OrderDao orderDao;
    @Autowired
    private PublisherDao publisherDao;
    @Autowired
    private OrderConverter orderConverter;
    @Autowired
    private OrderProcessorFactory orderProcessorFactory;
    @Autowired
    private BookService bookService;
    @Autowired
    private ShopService shopService;

    @Override
    public List<Order> getAllOrders() {
        logger.info(":::::::::::::::::::::::::All orders");
        List<OrderEntity> orderEntityList = orderDao.getAllOrders();
        List<Order> orders = orderConverter.convertToOrder(orderEntityList);
        logger.info(orders);
        return orders;
    }


    @Override
    public List<Order> getOutgoingOrders(int senderId) {
        List<OrderEntity> orderEntities = orderDao.getOutgoingOrders(senderId);
        return orderConverter.convertToOrder(orderEntities);
    }

    @Override
    public List<Order> getIncomingOrders(int recipientId) {
        List<OrderEntity> orderEntities = orderDao.getIncomingOrders(recipientId);
        logger.info("incoming order for id="+recipientId);
        for (OrderEntity orderEntity : orderEntities)
            logger.info(orderEntity.toString());
        return orderConverter.convertToOrder(orderEntities);
    }

    @Override
    public List<Order> getSatisfiedOutgoingOrders(int senderId) {
        List<OrderEntity> orderEntities = orderDao.getSatisfiedOutgoingOrders(senderId);
        return orderConverter.convertToOrder(orderEntities);
    }

    @Override
    public List<Order> getSatisfiedIncomingOrders(int recipientId) {
        List<OrderEntity> orderEntities = orderDao.getSatisfiedIncomingOrders(recipientId);
        return orderConverter.convertToOrder(orderEntities);
    }

    @Override
    public List<Order> getCompletedOutgoingOrders(int senderId) {
        List<OrderEntity> orderEntities = orderDao.getCompletedOutgoingOrders(senderId);
        return orderConverter.convertToOrder(orderEntities);
    }

    @Override
    public List<Order> getCompletedIncomingOrders(int recipientId) {
        List<OrderEntity> orderEntities = orderDao.getCompletedIncomingOrders(recipientId);
        return orderConverter.convertToOrder(orderEntities);
    }


    //========================================
    //========================================
    @Override
    public Order getById(int orderId) {
        OrderEntity orderEntity = orderDao.getOrderById(orderId);
        return orderConverter.convertToOrder(orderEntity);
    }

    @Override
    public float getPrice(Cart cart) {
        float price = 0;
        for (BookInCart bookInCart : cart.getBooks()) {
            if (bookInCart.getSeller().getType() == SubjectType.SHOP) {
                price += shopService.getBookPrice(bookInCart.getSeller().getId(), bookInCart.getBook().getId()) * bookInCart.getCount();
            } else if (bookInCart.getSeller().getType() == SubjectType.PUBLISHER) {
                price += publisherDao.getBookPrice(bookInCart.getBook().getId()) * bookInCart.getCount();
                //Is needed to consider discount??
            }
        }
        return price;
    }

    @Override
    public void addOrder(Order order) {
        RecipientOrderProcessor recipientOrderProcessor =
                orderProcessorFactory.getRecipientOrderProcessor(order.getRecipient());
        recipientOrderProcessor.addOrder(order);
    }

    @Override
    /**
     * Make order from existing shopping cart. Cart is divided on different orders.
     * It depend on seller and date of book released.
     */
    public void makeOrder(Account account, Payment payment, String address) {
        Cart cart = account.getCart();

        Subject sender = account.getSubject();
        while (!cart.getBooks().isEmpty()) {

            Subject recipient = cart.getBooks().get(0).getSeller();

            Order order = new Order();
            order.setSender(sender);
            order.setPayment(payment);
            order.setRecipient(recipient);
            order.setChangeDate(new Date());
            order.setState(OrderStatus.NEW);
            order.setAddress(address);
            List<BookInCart> booksForOrder = cart.getReleasedBooksBySeller(recipient.getName());

            if( ! booksForOrder.isEmpty()) {
                for (BookInCart bookInCart : booksForOrder) {
                    float bookPrice = bookService.getBookPrice(sender, recipient, bookInCart.getBook().getId());
                    order.addBook(bookInCart.getBook(), bookInCart.getCount(), bookPrice);
                }

                logger.info("RELEASED BOOK ORDER CREATING");
                addOrder(order);
            }

            booksForOrder = cart.getUnreleasedBooksBySeller(recipient.getName());
            if (! booksForOrder.isEmpty()) {
                for(BookInCart bookInCart : booksForOrder) {
                    order = new Order();
                    order.setSender(sender);
                    order.setPayment(payment);
                    order.setRecipient(recipient);
                    order.setAddress(address);
                    order.setState(OrderStatus.NEW);
                    order.setChangeDate(new Date());
                    List<BookInCart> listByDate = cart.getUnreleasedBooksByDate(bookInCart.getBook().getReleaseDate());

                    for (BookInCart unreleasedBook : listByDate) {
                        float bookPrice = bookService.getBookPrice(sender, recipient, unreleasedBook.getBook().getId());
                        order.addBook(bookInCart.getBook(), bookInCart.getCount(), bookPrice);
                    }

                    logger.info("UNRELEASED BOOK ORDER CREATING");
                    addOrder(order);
                }
            }
        }
    }

    @Override
    public BookInShopListWrapper makeOrderFromShopToPublisher(Account account, Payment payment, String address) {
        BookInShopListWrapper bookInShopListWrapper = new BookInShopListWrapper();

        Cart cart = account.getCart();
        Subject subject = account.getSubject();
        List<BookInCart> booksInCart = cart.getBooks();

        if (subject.getType() == SubjectType.SHOP) {
            for (BookInCart bookInCart : booksInCart) {
                BookInShop bookInShop = shopService.getBookInShop(bookInCart.getBook().getId(), subject.getId());
                if (bookInShop == null) {
                    bookInShop = new BookInShop();
                    bookInShop.setBook(bookInCart.getBook());
                    bookInShop.setCount(0);
                    float price = publisherDao.getBookPrice(bookInCart.getBook().getId());
                    bookInShop.setSellPrice(price);
                    bookInShop.setShop(subject);
                    bookInShopListWrapper.add(bookInShop);
                }
            }
        }

        makeOrder(account, payment, address);

        return bookInShopListWrapper;
    }

    ////Sender OrderService

    @Override
    public Order cancelOutgoingOrder(int orderId) {
        OrderEntity orderEntity = orderDao.getOrderById(orderId);
        Order order = orderConverter.convertToOrder(orderEntity);
        if ( canBeCanceled(order.getState()) ) {
            repealOrderWithStatus(order, OrderStatus.SENDER_CANCELED);
        }
        return getById(order.getId());
    }

    @Override
    public Order confirmOutgoingOrder(int orderId) {
        OrderEntity orderEntity = orderDao.getOrderById(orderId);
        Order order = orderConverter.convertToOrder(orderEntity);

        SenderOrderProcessor senderOrderProcessor = orderProcessorFactory.getSenderOrderProcessor(order.getSender());
        senderOrderProcessor.confirmOutgoingOrder(order);
        return getById(order.getId());
    }

    @Override
    public Order[] cancelBookFromOutgoingOrder(int orderId, int bookId) {
        OrderEntity orderEntity = orderDao.getOrderById(orderId);
        Order order = orderConverter.convertToOrder(orderEntity);

        if ( canBeCanceled(order.getState()) ) {
            return separateOrderWithStatus(order, bookId, OrderStatus.SENDER_CANCELED);
        } else {
            return new Order[] {order};
        }
    }

    ///recipient OrderService

    @Override
    /**return boolean - success state */
    public Order confirmIncomingOrder(int orderId) {
        OrderEntity orderEntity = orderDao.getOrderById(orderId);
        Order order = orderConverter.convertToOrder(orderEntity);

        RecipientOrderProcessor recipientOrderProcessor =
                orderProcessorFactory.getRecipientOrderProcessor(order.getRecipient());

        recipientOrderProcessor.confirmIncomingOrder(order);
        return order;
    }

    @Override
    /**
     * return order
     * Order could be rejected if it in states: NEW, WAITING_FOR_BOOK_RELEASES, WAITING_FOR_PAY
    */
    public Order rejectIncomingOrder(int orderId) {
        OrderEntity orderEntity = orderDao.getOrderById(orderId);
        Order order = orderConverter.convertToOrder(orderEntity);

        if ( canBeRejected(order.getState()) ) {
            repealOrderWithStatus(order, OrderStatus.RECIPIENT_REJECTED);
        }
        return getById(order.getId());
    }

    @Override
    /**@return Order[] - derived orders */
    public Order[] rejectBookFromIncomingOrder(int orderId, int bookId) {
        OrderEntity orderEntity = orderDao.getOrderById(orderId);
        Order order = orderConverter.convertToOrder(orderEntity);

        if ( canBeRejected(order.getState()) ) {
            return separateOrderWithStatus(order, bookId, OrderStatus.RECIPIENT_REJECTED);
        } else {
            return new Order[] {order};
        }
    }


    private void repealOrderWithStatus(Order order, OrderStatus orderState) {
        order.setState(orderState);
        order.setChangeDate(new Date());

        OrderEntity orderEntity = orderConverter.convertToOrderEntity(order);
        orderDao.saveOrUpdate(orderEntity);
    }

    private Order[] separateOrderWithStatus(Order order, int bookId, OrderStatus orderState) {
        if ( order.getRecords().size() > 1 ) {
            Order canceledOrder = separateBookFromOrder(order, bookId, orderState);

            //making separate order to reject and updating date
            if (canceledOrder != null) {
                logger.info(canceledOrder.toString());

                //updating change date and order cost
                order.setChangeDate(new Date());

                //converting
                OrderEntity orderEntity = orderConverter.convertToOrderEntity(order);
                OrderEntity canceledOrderEntity = orderConverter.convertToOrderEntity(canceledOrder);

                //saving
                orderDao.saveOrUpdate(orderEntity);
                Integer canceledOrderId = orderDao.save(canceledOrderEntity);

                return new Order[] {getById(order.getId()), getById(canceledOrderId)};
            }

        } else {
            //if there is only one book in the order cancel all order
            repealOrderWithStatus(order, orderState);
        }
        return new Order[] {getById(order.getId())};
    }

    /**
     * delete book entry with id equals "bookId" from orderEntity. create & return new OrderEntity with that book entry.
     * @param order
     * @param bookId
     * @param newOrderState
     * @return new OrderEntity with book entry with id equals "bookId" and orderState equals newOrderState
     * return null if there is no book with id equals bookId
     */
    private static Order separateBookFromOrder(Order order, int bookId, OrderStatus newOrderState) {
        //searching separated book
        OrderRecord separatedRecord = null;
        Iterator<OrderRecord> iter = order.getRecords().iterator();
        while (iter.hasNext()) {
            OrderRecord orderRecord = iter.next();
            if (orderRecord.getBook().getId() == bookId) {
                separatedRecord = orderRecord;
                iter.remove();
            }
        }

        //making separate order to reject and updating date
        if (separatedRecord != null) {
            //init
            Order separatedOrder = new Order();

            separatedOrder.setSender(order.getSender());
            separatedOrder.setRecipient(order.getRecipient());
            separatedOrder.setAddress(order.getAddress());
            separatedOrder.setPayment(order.getPayment());
            separatedOrder.setState(newOrderState);
            separatedOrder.setChangeDate(new Date());

            List<OrderRecord> separatedBooks = new ArrayList<OrderRecord>(1);
            separatedBooks.add(separatedRecord);
            separatedOrder.setRecords(separatedBooks);
            return separatedOrder;
        } else {
            return null;
        }
    }

    private boolean canBeCanceled(OrderStatus status) {
        return (status == OrderStatus.NEW ||
                status == OrderStatus.WAITING_FOR_BOOK_RELEASES ||
                status == OrderStatus.WAITING_FOR_PAY ||
                status == OrderStatus.WAITING_FOR_RECIPIENT_FEASIBILITY);
    }

    private boolean canBeRejected(OrderStatus status) {
        return (status == OrderStatus.NEW ||
                status == OrderStatus.WAITING_FOR_BOOK_RELEASES);
    }




}
